#ifndef SRC_CONSTANTS_H_
#define SRC_CONSTANTS_H_

#include <lldb/API/LLDB.h>
#include <string>

#include "src/error.h"

using lldb::SBTarget;

namespace llnode {

enum ConstantStatus { kInvalid, kValid, kLoaded };

// Class representing a constant which is used to interpret memory data. Most
// constants represent offset of fields on an object, bit-masks or "tags" which
// are used to identify types, but there are some constants with different
// meanings as well.
//
// By default, constants are loaded from the binary debug symbols, and usually
// those debug symbols are generated by V8's gen-postmortem-metadata.py or by
// Node.js' node-postmortem-metadata.cc. Some constants can also come from C++
// generated debug symbols.
//
// When a constant is successfully loaded, Check() and Loaded() will return
// true, which means we can safely use this constant and make assumptions based
// on its existence. In some cases, it's safe to assume defaults for a given
// constant. If that's the case, the constant will return false on Loaded() but
// true on Check(). A constant returning false for both Check() and Loaded() is
// not safe to use.
//
// Use the dereference operator (*constant) to access a constant value.
template <typename T>
class Constant {
 public:
  Constant() : value_(-1), status_(kInvalid) {}
  inline bool Check() {
    return (status_ == ConstantStatus::kValid ||
            status_ == ConstantStatus::kLoaded);
  }

  inline bool Loaded() { return status_ == kLoaded; }

  T operator*() {
    // TODO(mmarchini): Check()
    return value_;
  }

  inline std::string name() { return name_; }

  explicit Constant(T value) : value_(value), status_(kValid), name_("") {}
  Constant(T value, std::string name)
      : value_(value), status_(kLoaded), name_(name) {}

 private:
  T value_;
  ConstantStatus status_;
  std::string name_;
};

#define CONSTANTS_DEFAULT_METHODS(NAME) \
  inline NAME* operator()() {           \
    if (loaded_) return this;           \
    loaded_ = true;                     \
    Load();                             \
    return this;                        \
  }

class Constants {
 public:
  Constants() : loaded_(false) {}

  inline bool is_loaded() const { return loaded_; }

  void Assign(lldb::SBTarget target);

  inline virtual std::string constant_prefix() { return ""; };

  static Constant<int64_t> LookupConstant(SBTarget target, const char* name);

 protected:
  int64_t LoadRawConstant(const char* name, int64_t def = -1);
  int64_t LoadConstant(const char* name, int64_t def = -1);
  int64_t LoadConstant(const char* name, const char* fallback,
                       int64_t def = -1);
  Constant<int64_t> LoadConstant(std::initializer_list<const char*> names);
  Constant<int64_t> LoadOptionalConstant(
      std::initializer_list<const char*> names, int def);

  lldb::SBTarget target_;
  bool loaded_;
};

}  // namespace llnode

#endif
